/**
 * Copyright 2005 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.edi.manager.monitor;

import java.io.File;
import java.io.FileFilter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipFile;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import com.edi.Constant;
import com.edi.Kola;
import com.edi.client.plugin.Plugin;
import com.edi.client.plugin.PluginClassLoader;
import com.edi.client.plugin.PluginMethod;
import com.edi.client.plugin.ScriptPlugin;
import com.edi.client.plugin.StandardPlugin;
import com.edi.manager.PluginManager;
import com.edi.manager.ServiceManager;
import com.edi.pubsub.EdiService;
import com.edi.pubsub.LocalService;
import com.edi.util.FileHelper;

public class StandardPluginMonitor implements Runnable {
	private static final Log log = LogFactory.getLog(StandardPluginMonitor.class);
	private PluginManager pluginManager; 
	private ServiceManager serviceManager;
	private boolean running = false;
	public StandardPluginMonitor(PluginManager pluginManager,ServiceManager serviceManager){
		super();
		this.pluginManager=pluginManager; 
		this.serviceManager=serviceManager;
	}
	
	public void firstRun(){
		synchronized (this) {
            if (running) {
                return;
            }
            running = true;
        }
        try { 
            String workArea=System.getProperty(Constant.EDIPATH)+ Kola.SCRIPTPLUGIN_DIR;		
    		 File dir = new File(workArea);
    		 log.debug(dir.exists() +" dir = "+dir.getAbsolutePath());		
    		 File[] jars=dir.listFiles(new jarFilter()); 
    		 for(File jar:jars){ 
    			log.debug(" jar ="+jar.getAbsolutePath());
    			String pluginName=StringUtils.substringBeforeLast(jar.getName(), ".");
    			File unZipDir=new File(workArea,pluginName);
    			if(!unZipDir.exists()){
    				unZipJar(jar,unZipDir );				
    			}else if(unZipDir.lastModified()<jar.lastModified()){
    				unloadPlugin(unZipDir);
    				unZipJar(jar,unZipDir );
    			}
    		 }
    		 List<String> delPluginNames=new ArrayList<String>();
    		 delPluginNames.addAll(pluginManager.getStandardPlugins().keySet());
    		 File[] pluginDirs=dir.listFiles(new pluginDirFilter());
    		 for(File pluginDir:pluginDirs){
    			 File pluginXML=new File(pluginDir,"plugin.xml"); 
   			 if(!pluginXML.exists()){
   				 log.warn(" plugin.xml not exit :"+pluginDir.getName());
   				 continue;
   			 } 
   			 SAXReader reader =new SAXReader();
   			 reader.setEncoding("GB2312");
   			 Document pluginDoc=reader.read(pluginXML);
   			 Element nameElement=(Element)pluginDoc.selectSingleNode("/plugin/name");
   			 if(nameElement==null||StringUtils.isBlank(nameElement.getText().trim()))
   				 continue; 
   			 String standardPluginName=nameElement.getText().trim();  
    			if(!pluginManager.containsStandardPlugin(standardPluginName)){
    				loadPlugin(pluginDir);    
    			} 
    			delPluginNames.remove(standardPluginName);
    			log.debug("after pluginNames "+delPluginNames.size());
    		}
    		 for(String pluginName:delPluginNames){
    			 Plugin standPlugin=pluginManager.getStandardPlugin(pluginName);
    			 if(standPlugin!=null)
    				standPlugin.dispose();
    		 }
        }catch(Exception e){
       	 e.printStackTrace();
       	 log.error(" JarPluginMonitor 发生异常 :"+e);
        }
        synchronized (this) {
            running = false;
        }
	}
	
	public void run() { 
		log.debug(" enter JarPluginMonitor run ");
		 firstRun();
		log.debug(" end JarPluginMonitor run ");
		
	} 
	private class pluginDirFilter implements FileFilter{
		public boolean accept(File file){
			return file.isDirectory();
		}
	}
	private class jarFilter implements FileFilter{
		 public boolean accept(File file) {
			 String fileName = file.getName().toLowerCase();
             return (fileName.endsWith(".jar"));
		} 
	 }
	 /**
	  * 解压缩jar
	  * @param jar
	  */
	 private void unZipJar(File jar,File unZipDir){
		 log.debug(" enter unZipJar ");
		 try {
			ZipFile zipFile = new JarFile(jar);
			log.debug(" zipFile.getEntry(plugin.xml) ="+zipFile.getEntry("plugin.xml"));
			if(zipFile.getEntry("plugin.xml")==null) 
				return;
			unZipDir.mkdir();
			unZipDir.setLastModified(jar.lastModified());
			for(Enumeration emu=zipFile.entries();emu.hasMoreElements();){
				JarEntry entity=(JarEntry)emu.nextElement();
				File entryFile = new File(unZipDir, entity.getName()); 
				if(entity.getName().toLowerCase().endsWith("manifest.mf"))
					continue;
				if(!entity.isDirectory()){
					entryFile.getParentFile().mkdirs(); 
					FileOutputStream out=new FileOutputStream(entryFile);
					InputStream in=zipFile.getInputStream(entity);
					byte[] b=new byte[512];
					int len;
					while((len=in.read(b))!=-1){
						out.write(b, 0, len);
					}
					out.flush();
					out.close();
					in.close(); 
				}
			}
			zipFile.close();  
			log.debug(" end unZipJar ");
		} catch (IOException e) { 
			e.printStackTrace();
			log.error(" unZipJar 发生异常 : pluginName= "+jar.getName()+"  error  :"+e);
		}
	 }
	 /**
	  * 加载解压缩后的插件文件夹
	  * @param pluginDir
	  */
	 private void loadPlugin(File pluginDir){
		 log.debug(" // enter loadPlugin " +pluginDir.getName());
		 
		 StandardPlugin standardPlugin=new StandardPlugin();
		 try{
			 File pluginXML=new File(pluginDir,"plugin.xml"); 
			 if(!pluginXML.exists()){
				 log.warn(" plugin.xml not exit :"+pluginDir.getName());
				 return;
			 } 
			 SAXReader reader =new SAXReader();
			 reader.setEncoding("GB2312");
			 Document pluginDoc=reader.read(pluginXML);
			 Element root=(Element)pluginDoc.getRootElement();
			 Element nameElement=(Element)pluginDoc.selectSingleNode("/plugin/name");
			 Element descriptionElement=(Element)pluginDoc.selectSingleNode("/plugin/description");
			 Element authorElement=(Element)pluginDoc.selectSingleNode("/plugin/author");
			 Element versionElement=(Element)pluginDoc.selectSingleNode("/plugin/version");
			 Element dateElement=(Element)pluginDoc.selectSingleNode("/plugin/date");
			 Element ediServerVersionElement=(Element)pluginDoc.selectSingleNode("/plugin/ediServerVersion"); 
			 if(nameElement==null||StringUtils.isBlank(nameElement.getText().trim()))
				 return;
			 String name=nameElement.getText().trim(); 
			 standardPlugin.setPluginDir(pluginDir);
			 standardPlugin.setName(name);
			 standardPlugin.setDescription(descriptionElement==null?"":descriptionElement.getText().trim());
			 standardPlugin.setAuthor(authorElement==null?"":authorElement.getText().trim());
			 standardPlugin.setVersion(versionElement==null?"":versionElement.getText().trim());
			 standardPlugin.setDate(dateElement==null?"":dateElement.getText().trim());
			 standardPlugin.setEdiServerVersion(ediServerVersionElement==null?"":ediServerVersionElement.getText().trim()); 
			 
			 PluginClassLoader pluginLoader=pluginManager.loadClassLoader(name);
			 if(pluginLoader==null){
				 pluginLoader=new PluginClassLoader();
				 pluginLoader.addFolder(pluginDir);					 
			 }
			 if(!pluginManager.containsPluginClassLoader(name))
				 pluginManager.addClassLoader(name, pluginLoader); 
			 Iterator<Element> classIt=root.elementIterator("class");
			 while(classIt.hasNext()){
				 Element classElement=classIt.next(); 
				 String className=classElement.attributeValue("class"); 
				 String scriptType=classElement.attributeValue("type");
				 if(StringUtils.isBlank(className))
					 continue;
				 List<String> methodList=new ArrayList<String>();
				 Iterator<Element> methodIt=classElement.elementIterator("method");
				 while(methodIt.hasNext()){
					 Element methodElement=methodIt.next();
					 String methodName=methodElement.getText().trim();
					 if(StringUtils.isNotBlank(methodName))
						 methodList.add(methodName);
				 }  
//				 Class[] parameterTypes={Class.class};  
				 Class clazz=pluginLoader.loadClass(className);
				 Class[] ingerClazz=clazz.getInterfaces();
				 boolean implPlugin=false;
				 for(Class cla:ingerClazz){
					 log.debug("cla.getName() ="+cla.getName());
					 if(cla.getName().equalsIgnoreCase("com.edi.client.plugin.plugin")){
						 implPlugin=true;
						 break;
					 }
				 }
				 if(implPlugin&&"server".equalsIgnoreCase(scriptType.trim())){
					 log.debug(" server Plugin");
					 ClassLoader oldLoader = Thread.currentThread().getContextClassLoader(); 
					 Thread.currentThread().setContextClassLoader(pluginLoader); 
					 Plugin plugin=(Plugin)clazz.newInstance();
					 plugin.init(); 
					 Thread.currentThread().setContextClassLoader(oldLoader);  
				 } 
				 //管理服务
				 ScriptPlugin plugin = new ScriptPlugin(clazz,false);
				 Method[] methods = clazz.getDeclaredMethods(); 
				 for(Method method:methods){
					 if(!methodList.contains(method.getName()))
						 continue;
					 PluginMethod pluginMethod = new PluginMethod(method,clazz);
					 plugin.addMethod(method.getName(),pluginMethod);
					 String serviceName=name+":"+clazz.getName()+":"+method.getName();
					 EdiService ediService = new LocalService(serviceName);
					 serviceManager.addService(ediService);
					 log.debug("add  JAR serviceName  ="+serviceName); 
				 pluginManager.addScriptPlugin(plugin);
				 standardPlugin.addScriptPlugin(plugin);
				 }				
			 }
			 if(!pluginManager.containsStandardPlugin(name))
				 pluginManager.addStandardPlugin(name, standardPlugin); 
		 }catch(Exception e){
			e.printStackTrace();
			log.error(" loadPlugin 发生异常 : pluginName= "+pluginDir.getName()+"  error  :"+e);
		 } 
		 log.debug(" // end loadPlugin " +pluginDir.getName());		 
	 }
	 /**
	  * 卸载插件
	  * @param pluginDir
	  */
	 private void unloadPlugin(File pluginDir){
		 log.debug(" // enter unloadPlugin " +pluginDir.getName());
		 try{  
			 if(pluginDir.exists())
				 FileHelper.delFolder(pluginDir.getPath());
			 Plugin standardPlugin=pluginManager.getStandardPlugin(pluginDir.getName());
			 if(standardPlugin!=null)
				 standardPlugin.dispose(); 
		 }catch(Exception e){
			e.printStackTrace();
			log.error(" unloadPlugin 发生异常 : pluginName= "+pluginDir.getName()+"  error  :"+e);
		 }
		 log.debug(" // end unloadPlugin " +pluginDir.getName());		
		 
	 }

}

